/**
 * 节点构造器
 * MVVM Library
 * className: VNode
 * version: 0.0.1
 * nextUpdate: NaN
 * updateTime: 2021/05/28
 * @Author 洛水天依 <luotianyiovo@gmail.com>
 */
class VNode {
    vnodes = {
        tagName: '',
        // 子节点
        children: [],
        // 事件
        on: {},
        // 指令事件
        once: {
            if: '',
            for: ''
        },
        className: '',
        style: '',
        row: '',
        content: '',
        // 原dom对象
        dom: HTMLElement,
        // 父节点对象
        parent: '',
        // 当前节点对象
        owningNode: ''
    }

    // 数据对象
    data = []

    constructor(tagName, options = {
        // 子节点 children<VNode> = []
        children: [],
        // 事件
        on: [],
        // 指令事件
        once: {},
        className: '',
        style: '',
        row: '',
        content: '',
        // dom节点唯一key
        key: '',
        // 父节点对象
        parent: '',
        // 所属节点对象
        owningNode: ''
    }) {
        options.tagName = tagName
        this.vnodes = Object.assign(this.vnodes, options)
        this.data = this.vnodes.owningNode.data
    }

    // 遍历虚拟节点并生成真实dom
    traverseVNodeCreatDom(dom = null, data = null) {
        dom = dom || this
        if (dom instanceof Component) {
            return dom.getDomElement()
        }

        // 检测 是否动态调用 组件
        let name = getObjectValue(data, dom.vnodes.tagName.toLowerCase())
        if (typeof name === 'string') {
            let FC = buildComponents[name.toUpperCase()]
            if (FC) {
                if (FC instanceof Component) {
                    return FC.getDomElement()
                }
                buildComponents[name.toUpperCase()] = new Component(
                    FC.setParent(this.vnodes.parent).initialization(),
                    this.vnodes.owningNode,
                    FC.domObject
                ).setData(FC.data).setProps(this.vnodes.owningNode.options.data, data)
                return buildComponents[name.toUpperCase()].getDomElement()
            }
        }

        data = data || dom.data
        // 解析节点数据
        dom.vnodes.once = this.dynamicRendering(dom, dom.vnodes.dom, data)

        // 执行条件指令
        if (dom.vnodes.once['if'] && !dom.vnodes.once['if'].call()) {
            return false
        }

        let el = null

        // 初始化根节点
        if (dom.vnodes.once['for']) {
            // 如果调用for指令调用该方法解析
            el = this.VNodeOnceOfFor(dom, dom.vnodes.once['for'], data)
        } else {
            // 遍历子节点
            el = document.createElement(dom.vnodes.tagName)
            el.textContent = this.replaceInnerText(dom.vnodes.content || '', data)
            this.VNodeBindOnEvent(dom, el, data)
            this.VNodeBindStyle(dom.vnodes.dom, el, dom.vnodes, data)

            if (dom.vnodes.children instanceof Component) {
                el.appendChild(dom.vnodes.children.setProps(dom.vnodes.children?.data, data).getDomElement())
            } else {
                dom.vnodes.children?.map((d, key) => {
                    let edom = null
                    if (d instanceof Component) {
                        el.appendChild(d.setProps(d.parent?.data, data).getDomElement())
                    } else {
                        edom = this.traverseVNodeCreatDom(d, data)
                        if (edom !== false) {
                            el.appendChild(edom)
                            // 当前如果存在 条件指令 且下一条存在
                            if (d.vnodes.once['if'] && dom.vnodes.children[key + 1] && dom.vnodes.children[key + 1].vnodes.dom.getAttribute('i-else') !== null) {
                                delete dom.vnodes.children[key + 1]
                            }
                        }
                    }
                })
            }
        }
        return el
    }

    // 绑定事件
    VNodeBindOnEvent(vnode, el, data) {
        vnode.vnodes?.on.map(e => {
            let event = vnode.vnodes.dom.getAttribute(`@${e}`)
            if (event) {
                let params = [el]
                let events = event.split('(')
                if (events.length > 1) {
                    params = []
                    let paramNames = events[1].split(')')[0].split(',')
                    paramNames.map(e => {
                        params.push(this.linePrefix(e, data)?.replace(/'/g, ''))
                    })
                    if (params.length === 0) params = [el]
                }
                el.addEventListener(e, () => {
                    vnode.vnodes.owningNode.options.methods[events[0]].apply(vnode.vnodes.owningNode, params)
                })
            }
        })
    }

    // 绑定样式
    VNodeBindStyle(dom, el = HTMLElement, options = {}, object) {
        for (let index = 0; index < dom.attributes.length; index++) {
            if (dom.attributes[index].name.substr(0, 1) === ':') {
                el.setAttribute(dom.attributes[index].name.substr(1), this.replaceInnerText(dom.attributes[index].value, object))
                continue
            }

            // 解析指令并绑定事件
            if (dom.attributes[index].name.substr(0, 2) === 'i-') continue

            // @开头即忽略
            if (dom.attributes[index].name.substr(0, 1) === '@') continue

            if (dom.attributes[index].name === 'class') {
                el.setAttribute(dom.attributes[index].name, `${dom.attributes[index].value} ${options.className}`)
            } else if (dom.attributes[index].name === 'style') {
                el.setAttribute(dom.attributes[index].name, `${dom.attributes[index].value} ${options.style}`)
            } else {
                el.setAttribute(dom.attributes[index].name, dom.attributes[index].value)
            }
        }
    }

    // 执行循环指令
    VNodeOnceOfFor(dom, once, dataSource) {
        let data = once()

        let domChildrenLength = dom.vnodes.children.length

        let el = document.createElement(dom.vnodes.tagName)
        this.VNodeBindOnEvent(el, dom.vnodes.on)
        this.VNodeBindStyle(dom.vnodes.dom, el, dom.vnodes)

        for (let key in data.data) {
            let object = {
                key,
                ...dataSource
            }
            object[data.key] = data.data[key]
            if (domChildrenLength <= 0) {
                el.textContent += this.replaceInnerText(
                    dom.vnodes.row.replace(/[\r\n]/g, ''), object
                )
            } else {
                const children = new Array(dom.vnodes.children || [])[0] || []
                children.map((d, key) => {
                    let edom = d instanceof Component ? d.setProps(dom.vnodes.data, object).getDomElement() : this.traverseVNodeCreatDom(d, object)
                    if (edom) el.appendChild(edom)
                })
            }
        }
        return el
    }

    // 替换模型数据
    replaceInnerText(content, object) {
        content?.match(/{{([^}]*)}}/g)?.map(e => {
            let text = typeof object ===  'object' ? (
                getObjectValue(object, e.match(/{{(.*?)}}/)[1].trim()) || getObjectValue(this.data, e.match(/{{(.*?)}}/)[1].trim())
            ) || '' : object
            content = content.replace(
                e, typeof text === 'object' ? JSON.stringify(text) : text
            )
        })
        return content
    }

    // 解析指令
    dynamicRendering(dom, el = HTMLElement, data) {
        let once = {}
        for (const event in dom.vnodes.once) {
            let fn = el.getAttribute(`i-${event}`)
            if (fn) {
                once[event] = this.dynamicRenderingConditional(fn, event, el, data)
            }
        }
        return once
    }

    dynamicRenderingConditional(command, fn, el, data) {
        if (fn === 'if') return this.dynamicRenderingConditionalOfIf(command, el, data)
        if (fn === 'for') return this.dynamicRenderingConditionalOfFor(command, el, data)
    }

    // 解析 javascript IF 语句
    dynamicRenderingConditionalOfIf(command, el, data) {
        let code = ''
        // 获取 语句 外层运算符 条件语句
        let Operator = command.match(/[&|]{2,}/g) || []
        command.split(/[&|]{2,}/).map((value, key) => {
            value = value.trim()
            let m = value.match(/[=<>!]+/g)
            let vOperator = m? value.match(/[=<>!]+/g)[0] : ''
            let c = value.split(/[=<>!]+/)
            if (c.length > 1) {
                // 前置条件
                code += this.linePrefix(c[0], data)
                code += ` ${vOperator} ${this.linePrefix(c[1], data)} ${Operator[key] || ''} `
            } else {
                // c 长度为1
                let regx = c[0].match(/[&|>]+/g)
                let coding = ''
                c[0].split(/[&|>]+/).map((v, k) => {
                    coding = !regx ? this.linePrefix(c[0], data) || 'undefined' : `${this.linePrefix(v, data)} ${regx[k] || ''} `
                })
                code += ` ${coding} ${Operator[key] || ''} `
            }
        })
        return new Function(`return ${code} ? true : false`)
    }

    // 解析 javascript FOR 语句
    dynamicRenderingConditionalOfFor(command, el, sdata) {
        let key = command.split(' ')
        let data = command.replace(`${key[0]} ${key[1]}`, '')
        try {
            data = JSON.parse(data)
        } catch (e) {
            data = getObjectValue(sdata, key[2]) || []
        }
        return () => {
            return {
                data,
                key: key[0]
            }
        }
    }

    // 验证单条语句 并返回
    linePrefix (code, object) {
        let beforePrefix = code.trim().substr(0, 1)
        let afterPrefix = code.trim().substr(-1)
        code = code.replace(/[()]/g, '').trim()
        let sc = code.substr(0, 1)
        if (!((sc === '\'' || sc === '\"') || ['true', 'false', 'null', 'undefined'].includes(code) || !isNaN(Number(sc)))) {
            code = getObjectValue(object, code) || getObjectValue(this.data, code)
            code = code ? `'${code}'` : code
            code = beforePrefix !== '(' ?code : `${beforePrefix}${code}`
            code = afterPrefix !== ')' ?code : `${code}${afterPrefix}`
        }
        return code
    }
}